
/****
 * Copyright (C) 2006 dNux Team
 *                                                            ****
 * This program is free software; you can redistribute it and/or *
 * modify under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation;                    *
 * either version 2 of the License.                              *
 *                                                               *
 * This program is distributed in the hope that it will be       *
 * useful, but WITHOUT ANY WARRANTY; without even the implied    *
 * warranty of without even the implied waranty of               *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.          *
 * See the GNU General Public License for more details.          *
 *                                                               *
 * You should have received a copy of the                        *
 * GNU General Public License                                    *
 * along with this program; if not, write to the                 *
 * Free Software Foundation, Inc.,                               *
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. *
 *                                                               *
 ****                                                         ****/

#include "dhash_priv.h"

void
d_hash_init()
{
  OpenSSL_add_all_digests();
}

d_hash_t *
d_hash_new(d_error_t **error)
{
  d_hash_t *hash;
  hash = (d_hash_t *) malloc(sizeof(d_hash_t));
  if (!hash) {
    d_error(LIBDHASH, "d_hash_new: malloc error");
    goto error;
  }

  memset(hash, 0, sizeof(d_hash_t));
  hash->files = d_list_new();

 error:
  return hash;
}

void
d_hash_free(d_hash_t **hash)
{
  d_hash_t *h = *hash;
  if (h->digest_file) free(h->digest_file);
  if (h->files) d_list_free(&h->files); 
  if (h->results) d_map_free(&h->results);
  if (h->references) d_map_free(&h->references);
  if (h) free(h);
  *hash = NULL;
}

void
d_hash_add_file(d_hash_t *hash, char *file)
{
  d_list_pushback(hash->files, file);
}

void d_map_free_it(void *ptr) { d_map_free(&ptr); }

void
d_hash_add_reference(d_hash_t *hash, char *file, char *digest, char *value)
{
  d_map_iterator_t *it=NULL;
  d_map_data_t *new=NULL;
  d_map_data_t *data=NULL;

  if (! hash->references) hash->references = d_map_new();

  new = d_map_data_new(digest, value, free);

  it = d_map_find(hash->references, file);
  if (it) {
    d_map_data(it, &data);
  }
  else {
    data = d_map_data_new(file, d_map_new(), d_map_free_it);
    d_map_pushback(hash->references, data);
  }
  d_map_pushback((d_map_t *)data->value, new);  
}

d_bool_t
d_hash_check(d_hash_t *hash, d_error_t **error)
{ 
  d_bool_t ret = d_true;

  d_map_iterator_t *ref1_it=NULL;
  d_map_data_t     *ref1_data=NULL;
  d_map_iterator_t *ref2_it=NULL;
  d_map_data_t     *ref2_data=NULL;

  d_map_iterator_t *res1_it=NULL;
  d_map_data_t *res1_data=NULL;
  d_map_iterator_t *res2_it=NULL;
  d_map_data_t *res2_data=NULL;

  for(ref1_it=d_map_begin(hash->references); d_map_iterator_valid(ref1_it);
      d_map_next(&ref1_it)) {
    d_map_data(ref1_it, &ref1_data);

    for(ref2_it=d_map_begin(ref1_data->value); d_map_iterator_valid(ref2_it);
	d_map_next(&ref2_it)) {
      d_map_data(ref2_it, &ref2_data);
      
      res1_it = d_map_find(hash->results, ref1_data->key);
      if (res1_it) {
	d_map_data(res1_it, &res1_data);
	res2_it = d_map_find((d_map_t *)res1_data->value, ref2_data->key);
	if (res2_it) {
	  d_map_data(res2_it, &res2_data);
	  if (strcmp((char *) res2_data->value, (char *) ref2_data->value)) {
	    ret = d_false;
	  }
	}
	d_map_free_iterator(&res2_it);
      }
      d_map_free_iterator(&res1_it);
    }
  }
  d_map_free_iterator(&ref1_it);

  return ret;
}

void
d_hash_display(d_hash_t *hash)
{
  fprintf(stdout, "== files ==\n");
  d_hash_display_files(hash);
  fprintf(stdout, "\n== result ==\n");
  d_hash_display_result(hash);
  fprintf(stdout, "\n== references ==\n");
  d_hash_display_references(hash);
  fprintf(stdout, "\n");
}


void
d_hash_display_files(d_hash_t *hash)
{
  char *file=NULL;
  d_list_iterator_t *list_it=NULL;

  for(list_it = d_list_begin(hash->files); d_list_iterator_valid(list_it);
      d_list_next(&list_it)) {
    d_list_value(list_it, &file);
    fprintf(stdout, "%s\n", file);
  }
  d_list_free_iterator(&list_it);
}

void
d_hash_display_references(d_hash_t *hash)
{
  d_map_iterator_t *map1_it=NULL;
  d_map_data_t     *map1_data=NULL;

  d_map_iterator_t *map2_it=NULL;
  d_map_data_t     *map2_data=NULL;

  for(map1_it = d_map_begin(hash->references); d_map_iterator_valid(map1_it);
      d_map_next(&map1_it)) {
    d_map_data(map1_it, &map1_data);
    fprintf(stdout, "%s:\n", map1_data->key);
    for(map2_it = d_map_begin(map1_data->value); d_map_iterator_valid(map2_it);
	d_map_next(&map2_it)) {
      d_map_data(map2_it, &map2_data);
      fprintf(stdout, "%s=%s\n", map2_data->key, (char *)map2_data->value);
    }
    d_map_free_iterator(&map2_it);
    fprintf(stdout, "\n");
  }
  d_map_free_iterator(&map1_it);
}

void
d_hash_display_diff(d_hash_t *hash)
{
  d_map_iterator_t *ref1_it=NULL;
  d_map_data_t     *ref1_data=NULL;
  d_map_iterator_t *ref2_it=NULL;
  d_map_data_t     *ref2_data=NULL;

  d_map_iterator_t *res1_it=NULL;
  d_map_data_t *res1_data=NULL;
  d_map_iterator_t *res2_it=NULL;
  d_map_data_t *res2_data=NULL;

  for(ref1_it=d_map_begin(hash->references); d_map_iterator_valid(ref1_it);
      d_map_next(&ref1_it)) {
    d_map_data(ref1_it, &ref1_data);

    fprintf(stdout, "%s:\n", ref1_data->key);

    for(ref2_it=d_map_begin(ref1_data->value); d_map_iterator_valid(ref2_it);
	d_map_next(&ref2_it)) {
      d_map_data(ref2_it, &ref2_data);
      
      res1_it = d_map_find(hash->results, ref1_data->key);
      if (res1_it) {
	d_map_data(res1_it, &res1_data);
	res2_it = d_map_find((d_map_t *)res1_data->value, ref2_data->key);
	if (res2_it) {
	  d_map_data(res2_it, &res2_data);
	  if (!strcmp((char *) res2_data->value, (char *) ref2_data->value)) {
	    fprintf(stdout, "%s: ok\n", (char *) ref2_data->key);
	  }
	  else {
	    fprintf(stdout, "%s: bad\n", (char *) ref2_data->key);
	  }
	}
	d_map_free_iterator(&res2_it);
      }
      d_map_free_iterator(&res1_it);
    }
    fprintf(stdout, "\n");
  }
  d_map_free_iterator(&ref1_it);
}

void
d_hash_display_result(d_hash_t *hash)
{
  d_map_iterator_t *map1_it=NULL;
  d_map_data_t     *map1_data=NULL;

  d_map_iterator_t *map2_it=NULL;
  d_map_data_t     *map2_data=NULL;

  for(map1_it = d_map_begin(hash->results); d_map_iterator_valid(map1_it);
      d_map_next(&map1_it)) {
    d_map_data(map1_it, &map1_data);
    fprintf(stdout, "%s:\n", map1_data->key);
    for(map2_it = d_map_begin(map1_data->value); d_map_iterator_valid(map2_it);
	d_map_next(&map2_it)) {
      d_map_data(map2_it, &map2_data);
      fprintf(stdout, "%s=%s\n", map2_data->key, (char *)map2_data->value);
    }
    d_map_free_iterator(&map2_it);
    fprintf(stdout, "\n");
  }
  d_map_free_iterator(&map1_it);
}

void
d_hash_display_result_xml(d_hash_t *hash)
{
  d_map_iterator_t *map1_it=NULL;
  d_map_data_t     *map1_data=NULL;

  d_map_iterator_t *map2_it=NULL;
  d_map_data_t     *map2_data=NULL;

  for(map1_it = d_map_begin(hash->results); d_map_iterator_valid(map1_it);
      d_map_next(&map1_it)) {
    d_map_data(map1_it, &map1_data);
    fprintf(stdout, "%s:\n", map1_data->key);
    for(map2_it = d_map_begin(map1_data->value); d_map_iterator_valid(map2_it);
	d_map_next(&map2_it)) {
      d_map_data(map2_it, &map2_data);
      fprintf(stdout, "<%s>%s</%s>\n", map2_data->key,
	      (char *)map2_data->value,
	      map2_data->key);
    }
    d_map_free_iterator(&map2_it);
  }
  d_map_free_iterator(&map1_it);
}

d_map_t*
d_hash_get_result(d_hash_t *hash)
{
  return hash->results;
}

void
d_hash_set_files(d_hash_t *hash, d_list_t *files)
{
  if (! hash) goto error;
  if (! files) goto error;

  if (hash->files) d_list_free(&hash->files);
  hash->files = files;

 error:
  return;
}

void
d_hash_set_references(d_hash_t *hash, d_map_t *references)
{
  if (! hash) goto error;
  if (! references) goto error;

  if (hash->references) d_map_free(&hash->references);
  hash->references = references;

 error:
  return;
}

void
d_hash_perform_references(d_hash_t *hash, d_error_t **error)
{
  d_map_iterator_t *map1_it=NULL;
  d_map_data_t     *map1_data=NULL;

  d_map_iterator_t *map2_it=NULL;
  d_map_data_t     *map2_data=NULL;

  for(map1_it = d_map_begin(hash->references); d_map_iterator_valid(map1_it);
      d_map_next(&map1_it)) {
    d_map_data(map1_it, &map1_data);
    for(map2_it = d_map_begin(map1_data->value); d_map_iterator_valid(map2_it);
	d_map_next(&map2_it)) {
      d_map_data(map2_it, &map2_data);
      d_hash_perform_digest_file(hash, map1_data->key, map2_data->key, error);
    }
    d_map_free_iterator(&map2_it);
  }
  d_map_free_iterator(&map1_it);  
}

void
d_hash_perform_digest(d_hash_t *hash, char *digest, d_error_t **error)
{
  char *file=NULL;
  d_list_iterator_t *it=NULL;

  if (hash == NULL) {
    d_error(LIBDHASH, "hash cannot be null");
    goto error;
  }
  if (hash->files == NULL) {
    d_error(LIBDHASH, "files cannot be null");
    goto error;
  }
  if (digest == NULL) {
    d_error(LIBDHASH, "digest cannot be null");
    goto error;
  }

  for (it=d_list_begin(hash->files); d_list_iterator_valid(it);
       d_list_next(&it)) {
    d_list_value(it, &file);
    d_hash_perform_digest_file(hash, file, digest, error);
  }
  d_list_free_iterator(&it);

 error:
  return;
}

void
d_hash_perform_digest_file(d_hash_t *hash, char *file, char *digest,
			   d_error_t **error)
{
  EVP_MD_CTX mdctx;
  const EVP_MD *md;
  
  int i;
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len;

  char buffer[2048];
  char *shash = NULL;

  FILE *f=NULL;
  size_t size;

  if (! hash) {
    d_error(LIBDHASH, "hash cannot be NULL");
    goto error;
  }
  if (! file) {
    d_error(LIBDHASH, "file cannot be NULL");
    goto error;
  }
  if (! digest) {
    d_error(LIBDHASH, "digest cannot be NULL");
    goto error;
  }

  f = fopen(file, "r");
  if (f == NULL) {
    d_error(LIBDHASH, "file not found");
    return;
  }

  md = EVP_get_digestbyname(digest);
  if (md == NULL) {
    d_error(LIBDHASH, "digest unknown.");
    return;
  }

  EVP_MD_CTX_init(&mdctx);
  EVP_DigestInit_ex(&mdctx, md, NULL);
  while( (size = fread(buffer, 1, sizeof(buffer), f)) > 0) {
    EVP_DigestUpdate(&mdctx, buffer, size);
  }

  EVP_DigestFinal_ex(&mdctx, md_value, &md_len);
  EVP_MD_CTX_cleanup(&mdctx);

  fclose(f);

  shash = (char *) malloc(sizeof(char) * (2 * md_len + 1));
  for (i=0; i < md_len; i++) {
    snprintf(&(shash[i*2]), 3, "%02x", md_value[i]);
  }
  shash[i*2] = '\0';
  

  d_hash_result_add(hash, file, digest, shash);
 error:
  return;
}

void
d_hash_result_add(d_hash_t *hash, char *file, char *digest, char *value)
{
  d_map_iterator_t *it=NULL;
  d_map_data_t *new=NULL;
  d_map_data_t *data=NULL;

  if (! hash->results) hash->results = d_map_new();

  new = d_map_data_new(digest, value, free);

  it = d_map_find(hash->results, file);
  if (it) {
    d_map_data(it, &data);
  }
  else {
    data = d_map_data_new(file, d_map_new(), d_map_free_it);
    d_map_pushback(hash->results, data);
  }
  d_map_pushback((d_map_t *)data->value, new);
}

void
d_hash_result_save(d_hash_t *hash, char *file, d_error_t **error)
{
  FILE *f=NULL;

  d_map_iterator_t *map1_it=NULL;
  d_map_data_t     *map1_data=NULL;

  d_map_iterator_t *map2_it=NULL;
  d_map_data_t     *map2_data=NULL;

  if (! hash) {
    d_error(LIBDHASH, "hash cannot be NULL");
    goto error;
  }
  if (! file) {
    d_error(LIBDHASH, "file cannot be NULL");
    goto error;
  }

  f = fopen(file, "w+");
  if (! f) {
    d_error(LIBDHASH, "cannot open file");
    goto error;
  }

  for(map1_it = d_map_begin(hash->results); d_map_iterator_valid(map1_it);
      d_map_next(&map1_it)) {
    d_map_data(map1_it, &map1_data);
    fprintf(f, "%s\n", map1_data->key);
    for(map2_it = d_map_begin(map1_data->value); d_map_iterator_valid(map2_it);
	d_map_next(&map2_it)) {
      d_map_data(map2_it, &map2_data);
      fprintf(f, "%s=%s\n", map2_data->key, (char *)map2_data->value);
    }
    d_map_free_iterator(&map2_it);
    fprintf(f, "\n");
  }
  d_map_free_iterator(&map1_it);
 error:
  if (f) fclose(f);
  return;
}

void
d_hash_references_load(d_hash_t *hash, char *file, d_error_t **error)
{
  char *current=NULL;
  char line[512];
  char *value=NULL;

  FILE *f=NULL;
  
  if (! hash) {
    d_error(LIBDHASH, "hash cannot be NULL");
    goto error;
  }

  if (! file) {
    d_error(LIBDHASH, "file cannot be NULL");
    goto error;
  }

  f = fopen(file, "r");
  if (! f) {
    d_error(LIBDHASH, "cannot open file.");
    goto error;
  }

  while ( fscanf(f, "%511s\n", line) != EOF) {
    if (( value = strchr(line, '='))) {
      *value='\0';
      value++;
      if (current)
	d_hash_add_reference(hash, strdup(current), strdup(line), strdup(value));
    }
    else {
      if (current) free(current);
      current = strdup(line);
    }
  }

 error:
  if (f) fclose(f);
  return;
}
